/* eslint-disable @typescript-eslint/no-explicit-any */
import { Component, type ObjectProps } from '@knno/jsx';
import { isDescendant, isInSvg } from '../tools/utils';
import { Mask } from './mask/mask';
import { popupStyle } from './popup/popup.css';
import { DATA } from './symbol';

interface ActionHandler {
	readonly current: unknown;
	close(esc: boolean): void;
}

const openedDialogs: DialogBase<any>[] = [];

function isPopupActive(): boolean {
	let elem = document.activeElement;
	while (elem) {
		if (elem.classList.contains(popupStyle)) {
			return true;
		}
		elem = elem.parentElement;
	}
	return false;
}

/**
 * 限制焦点在对话框内
 */
function confineFocus() {
	if (openedDialogs.length <= 0) return;
	if (isPopupActive()) return;
	const lastDialog = openedDialogs[openedDialogs.length - 1];
	if (!document.activeElement || !isDescendant([lastDialog.el], document.activeElement)) {
		lastDialog.focus();
	}
}

if (document !== undefined) {
	document.documentElement.addEventListener('focusin', confineFocus);
}

function addDialog(dlg: DialogBase<any>) {
	if (openedDialogs.length > 0) {
		const last = openedDialogs[openedDialogs.length - 1];
		last.el.classList.add('pre-covered');
		setTimeout(() => {
			last.el.classList.add('covered');
		}, 16);
	}
	openedDialogs.push(dlg);
	confineFocus();
}

function removeDialog(dlg: DialogBase<any>) {
	const idx = openedDialogs.indexOf(dlg);
	let previous: DialogBase<any> | undefined;
	if (idx >= 0) {
		previous = openedDialogs[idx - 1];
		openedDialogs.splice(idx, 1);
	}
	if (openedDialogs.length > 0) {
		const last = openedDialogs[openedDialogs.length - 1];
		if (last === previous) {
			last.el.classList.remove('covered');
			setTimeout(() => {
				last.el.classList.remove('pre-covered');
			}, 190);
		}
	}
}

type OpenDialogOptions = {
	focusElement?: HTMLElement;
	delay?: number;
};

type CloseResult = {
	result?: unknown;
	esc?: boolean;
};

export abstract class DialogBase<C extends DialogBase<C>> extends Component<
	ObjectProps<C>,
	C,
	HTMLDivElement
> {
	protected [DATA]: {
		visible: boolean;
		autoClose: boolean;
		escClose: boolean;
		title?: string;
		noBar?: boolean;
		onOk?: (this: C) => void;
		onOpen?: (this: C) => void;
		onClose?: (this: C, result: CloseResult) => void;
		onWillClose?: (this: C, result: CloseResult) => void;
	} = {
		visible: false,
		autoClose: false,
		escClose: false,
	};

	static actionHandler?: ActionHandler;

	abstract get panel(): HTMLElement;

	constructor(props: ObjectProps<unknown>) {
		super(props);
		this.el.addEventListener('mousedown', (evt) => {
			if (isInSvg(evt.target as Element)) {
				return;
			}
			const isParent = !isDescendant([this.panel], evt.target as HTMLElement);
			if (isParent && this.autoClose) {
				this.close();
			}
		});
		this.el.addEventListener('open', () => {
			this[DATA].onOpen?.call(this as unknown as C);
		});
		this.el.addEventListener('close', (evt) => {
			const result = (evt as CustomEvent).detail as CloseResult | undefined;
			this.close(result);
			this[DATA].onClose?.call(this as unknown as C, result ?? {});
		});
		setTimeout(() => {
			this.panel.addEventListener('keydown', (evt) => {
				if (evt.key === 'Escape' && (this.autoClose || this.escClose)) {
					evt.stopPropagation();
					this.close({ esc: true });
				}
			});
		});
	}

	onOpen(cb: ((this: C) => void) | undefined): this {
		this[DATA].onOpen = cb;
		return this;
	}

	onClose(cb: ((this: C, result: CloseResult) => void) | undefined): this {
		this[DATA].onClose = cb;
		return this;
	}
	onWillClose(cb: ((this: C, result: CloseResult) => void) | undefined): this {
		this[DATA].onWillClose = cb;
		return this;
	}

	show(options?: OpenDialogOptions): this {
		if (this[DATA].visible) {
			return this;
		}
		if (DialogBase.actionHandler?.current) DialogBase.actionHandler.close(false);
		this[DATA].visible = true;
		const delay = options?.delay ?? 0;
		Mask.show(delay);
		document.body.append(this.el);
		if (delay) {
			setTimeout(
				() => {
					this.el.classList.add('active');
				},
				delay > 16 ? delay : 16,
			);
		} else {
			setTimeout(() => {
				this.el.classList.add('active');
			}, 16);
		}
		setTimeout(() => {
			this.el.dispatchEvent(new Event('open'));
			if (options?.focusElement) {
				options?.focusElement.focus();
			} else {
				this.panel.focus();
			}
		}, options?.delay ?? 16);
		addDialog(this);
		return this;
	}

	close(result?: CloseResult): this {
		if (!this[DATA].visible) {
			return this;
		}
		this[DATA].visible = false;
		this.el.classList.remove('active');
		this[DATA].onWillClose?.call(this as unknown as C, result ?? {});
		setTimeout(() => {
			this.el.remove();
			this.el.dispatchEvent(new CustomEvent('close', { detail: result }));
		}, 200);
		removeDialog(this);
		Mask.close();
		return this;
	}

	get autoClose(): boolean {
		return this[DATA].autoClose;
	}
	setAutoClose(value: boolean): this {
		this[DATA].autoClose = value;
		return this;
	}

	get escClose(): boolean {
		return this[DATA].escClose;
	}
	setEscClose(value: boolean): this {
		this[DATA].escClose = value;
		return this;
	}

	focus(options?: FocusOptions | undefined): this {
		this.panel.focus(options);
		return this;
	}
}
